<template>
  <div class='app-container'>
    <div ref="graphContainer" class="parent-wrapper">
      <a-button type="primary" style="position: absolute; top: 15px; left: 20px;" @click="createGraph(false)">创建组</a-button>
      <a-button type="primary" style="position: absolute; top: 15px; left: 110px;" @click="createGraph(true)">创建环形组</a-button>
    </div>
  </div>
</template>

<script>
import mxgraph from '@utils/init.js';

export default {
  name: '',
  module_index: 300,
  module_name: '右键+删除+状态+键盘',
  data() {
    return {
      graph: null,
      parent: null,
      num: 0,
      undoMng: null,    // 撤销管理器，可以实现操作的撤销
      keyHandler: null, // 键盘事处理器

     
    };
  },
  created() { },
  mounted() {
    this.init();
    // rightBtn将右键的响应事件进行了重写，但是此时的菜单是没有样式和响应事件的
    // 样式是依赖style部分优化的(如果加上scoped会使style无效化，但是也可以放在全局样式中)，其中定位的详细位置是mxgraph赋值的
    this.rightBtn();

    // 初始化undo/redo，也就是创建了一个撤销管理器
    this.initUndoManager();
    // 初始化键盘事件
    this.initKeyHandler();
  },
  methods: {
    init() {
      // 创建全局容器
      this.graph = new mxGraph(this.$refs.graphContainer);

      // 创建全局父元素
      this.parent = this.graph.getDefaultParent();
    },
    // 创建组
    createGraph(isAnnulus) {
      ++this.num;
      var v1 = this.graph.insertVertex(this.parent,'Hello','Hello',Number(((this.num) + Math.random() * 1080).toFixed(2)),Number(((this.num) + Math.random() * 570).toFixed(2)),80,30);
      ++this.num;
      var v2 = this.graph.insertVertex(this.parent,'World','World',Number(((this.num) + Math.random() * 1080).toFixed(2)),Number(((this.num) + Math.random() * 570).toFixed(2)),80,30);
      ++this.num;
      var v3 = this.graph.insertVertex(this.parent,'everyBody!','everyBody!',Number(((this.num) + Math.random() * 1080).toFixed(2)),Number(((this.num) + Math.random() * 570).toFixed(2)),60,60);
      this.graph.insertEdge(v1,'v1-v2','',v1,v2);
      this.graph.insertEdge(v2,'v2-v3','',v2,v3,'edgeStyle=orthogonalEdgeStyle;');
      isAnnulus && this.graph.insertEdge(v3,'v3-v1','',v3,v1) && this.$message.info('对环形组使用级联删除将导致死循环');
    },
    // 重建右键菜单
    rightBtn() {
      // mxEvent.disableContextMenu：禁用给定元素的上下文菜单
      mxEvent.disableContextMenu(this.$refs.graphContainer)
      // 初始化上下文菜单，也就是初始化创建了右键的菜单，虽然实现了事件的绑定，但此时的右键菜单是没有样式的
      // popupMenuHandler：创建并返回将在此图中使用的新mxPopupMenuHandler    
      // mxPopupMenuHandler：创建弹出菜单的事件处理程序。
      // factoryMethod：构造一个创建mxPopupMenu的事件处理程序。
      this.graph.popupMenuHandler.factoryMethod = (menu /*, cell*/) => {
        menu.addItem('单体删除',null,() => {
          // getSelectionCells: 返回所选mxCells的数组。
          this.removeNode(this.graph.getSelectionCells())
        })
        menu.addItem('带箭头删除',null,() => {
          // getSelectionCells: 返回所选mxCells的数组。
          this.removeNode(this.graph.getSelectionCells(),true)
        })
        menu.addItem('级联删除',null,() => {
          // getSelectionCells: 返回所选mxCells的数组。
          this.removeNode(this.graph.getSelectionCells(),true,true)
        })
        // addSeparator：在给定父项目中添加水平分隔符，如果未指定父项目，则在顶级菜单中添加水平分隔符。
        menu.addSeparator()
        menu.addItem('撤回',null,() => {
          this.undoMng.undo();
        })
        menu.addItem('撤销撤回',null,() => {
          this.undoMng.redo()
        })
      }
    },
    // 删除
    /**
     * cells: 所选中的节点的数组，默认可以通过ctrl+左键的方式多选，所以所谓的单体删除在多选的情况下也可以多个一起删除
     * includeEdges：是否在删除节点的时候将节点所关联的箭头（发出的和指向的都算）一起删除
     * multilevel：是否级联删除，也就是被节点发出的箭头指向的节点是否要一起删除
     */
    removeNode(cells = [],includeEdges = false,multilevel = false) {
      // 未选择则直接回退
      if (!cells.length) return;

      multilevel || this.graph.removeCells(cells,includeEdges) && console.log('非级联删除');     // 短路求值
      if (!multilevel) return;  // 如果不是级联删除，到这里就可以执行完毕了 

      // 这里转化为Set是因为Set类型会自动去重，不会出现重复元素，避免了在寻找级联元素时重复寻找一个节点的情况
      let tmpSet = new Set(cells)
      // 如果通过ctrl+左键单击一次性选择了多个级联的开始节点，那么每个节点都应单独进行级联删除
      // 通过forEach来确保每一个节点的子节点都被选择；通过Set的数据类型确保不会重复删除一个节点
      cells.forEach((cell) => {
        this.seekCorrCell(cell,tmpSet)
      });
      this.graph.removeCells(Array.from(tmpSet),includeEdges)

    },
    // 找到当前节点所关联的所有下级节点（被当前节点所发出箭头指向的节点）
    seekCorrCell(cell,deleteSet) {
      deleteSet.add(cell);
      // cell.edges：当前cell节点所连接的边，无论是指向这个节点的还是这个节点发出的
      if (cell.edges) {
        console.log(cell.edges)
        cell.edges.forEach(tmpedge => {
          // tmpEdge.target：引用目标终端。也就是这个边指向的目标 
          // tmpEdge.target !== cell 也就是说如果这个边指向的目标不是当前节点，那么就把被指向的节点加入deleteSet待删除数组中，然后递归调用本函数，最终将节点所有下级节点全部删除
          // 此时如果出现cell的闭环则会让程序死循环导致异常
          if (tmpedge.target != cell) {
            deleteSet.add(tmpedge.target);
            this.seekCorrCell(tmpedge.target,deleteSet)
          }
        })
      }
    },
    // 创建状态管理器，右键事件中的撤回和撤销撤回就是依赖此管理器来运行的
    initUndoManager() {
      // 创建了一个撤销管理器，可以实现操作的撤销
      this.undoMng = new mxUndoManager();

      // 这个函数是给下面的监听用的
      let listen = (sender,evt) => {
        this.undoMng.undoableEditHappened(evt.getProperty('edit'))
      }
      // 对graph面板添加监听
      // getModel: 返回包含单元格的mxGraphModel。
      // addListener: 将指定的函数绑定到给定的事件名。如果没有提供事件名称，则为所有事件注册侦听器。
      // 侦听器的参数是发送器和mxEventObject
      this.graph.getModel().addListener(mxEvent.UNDO,listen)
      this.graph.getView().addListener(mxEvent.UNDO,listen)
    },
    // 创建键盘事件
    initKeyHandler() {
      // 初始化键盘事件监听
      this.keyHandler = new mxKeyHandler(this.graph);

      // bindControlKey：将指定的键码绑定到给定的函数。
      this.keyHandler.bindControlKey(90,() => {
        console.log("按下了ctrl+Z")
        this.undoMng.undo();
      })
      this.keyHandler.bindControlKey(89,() => {
        console.log("按下了ctrl+Y")
        this.undoMng.redo()
      })

      this.keyHandler.bindKey(46,() => {
        this.removeNode(this.graph.getSelectionCells())
      })
    },
  }
};
</script>

<style>
body div.mxPopupMenu {
  -webkit-box-shadow: 3px 3px 6px #c0c0c0;
  -moz-box-shadow: 3px 3px 6px #c0c0c0;
  box-shadow: 3px 3px 6px #c0c0c0;
  background: white;
  position: absolute;
  border: 3px solid #e7e7e7;
  padding: 3px;
}

body table.mxPopupMenu {
  border-collapse: collapse;
  margin: 0;
}

body tr.mxPopupMenuItem {
  color: black;
  cursor: default;
}

body td.mxPopupMenuItem {
  padding: 3px 30px 3px 10px;
  font-family: Arial;
  font-size: 10pt;
}

body td.mxPopupMenuIcon {
  background-color: white;
  padding: 0;
}

body tr.mxPopupMenuItemHover {
  background-color: #eeeeee;
  color: black;
}

table.mxPopupMenu hr {
  border-top: solid 1px #cccccc;
}

table.mxPopupMenu tr {
  font-size: 4pt;
}
</style>